﻿namespace GreenIsomer.Web.Controls.JQuery {
	using System;
	using System.Collections.Specialized;
	using System.Web.UI;
	using System.Web.UI.WebControls;
	using System.ComponentModel;
	using System.Globalization;
	using System.Diagnostics.CodeAnalysis;

	/// <summary>
	/// Server control used to generate a jQuery UI Datepicker widget.
	/// </summary>
	public class DatePicker : JQueryWebControl, IPostBackDataHandler {

		// TODO: Serialize/Deserialize DayNames*, MonthNames* properties to ViewState

		#region Constants: Property defaults

		private const bool defaultButtonImageOnly = false;
		private const string defaultButtonText = "...";
		private const bool defaultChangeFirstDay = true;
		private const bool defaultChangeMonth = false;
		private const bool defaultChangeYear = false;
		private const string defaultCloseText = "Close";
		private const bool defaultConstrainInput = true;
		private const string defaultCurrentText = "Today";
		private const string defaultDurationString = "Normal";
		private const int defaultFirstDay = 0;
		private const bool defaultGoToCurrent = false;
		private const bool defaultHideIfNoPrevNext = false;
		private const bool defaultIsRtl = false;
		private const bool defaultNavigationAsDateFormat = false;
		private const string defaultNextText = "Next>";
		private const int defaultRowCount = 1;
		private const int defaultColumnCount = 1;
		private const string defaultPrevText = "<Prev";
		private const string defaultShortYearCutoffString = "+10";
		private const string defaultShowAnim = "show";
		private const bool defaultShowButtonPanel = false;
		private const DropDownShowOn defaultShowOn = DropDownShowOn.Focus;
		private const bool defaultShowOtherMonths = false;
		private const int defaultStepMonths = 1;
		//TODO: Decide if a fancy Struct is the way to go for yearRange
		private const int defaultYearRangeStart = -10;
		private const int defaultYearRangeEnd = 10;
		private const bool defaultYearRangeAbsolute = false;

		#endregion


		#region Static Fields: Property defaults

		//If changing these values, be sure to change the corresponding string constant to match
		private static readonly Duration defaultDuration = JQuery.Duration.Normal;
		private static readonly RelativeValue defaultShortYearCutoff = new RelativeValue(true, 10);

		#endregion


		#region Static Methods: Private routines

		private static string GetText(DateTime? date) {
			if(date.HasValue) return date.Value.ToString("MM/dd/yyyy", CultureInfo.InvariantCulture);
			return string.Empty;
		}

		private static DateTime? ParseDate(string text) {
			DateTime value;
			if(DateTime.TryParse(text, out value)) return value.Date;
			return null;
		}

		#endregion


		#region Fields

		private DayNameCollection dayNames;
		private DayNameCollection dayNamesShort;
		private DayNameCollection dayNamesMin;
		private MonthNameCollection monthNames;
		private MonthNameCollection monthNamesShort;

		#endregion


		#region Constructors

		/// <summary>
		/// Initializes a new instance of the GreenIsomer.Web.Controls.JQuery.DatePicker class.
		/// </summary>
		public DatePicker() : base(HtmlTextWriterTag.Input) {
			this.dayNames = new DayNameCollection(DayNameLength.Normal);
			this.dayNamesShort = new DayNameCollection(DayNameLength.Short);
			this.dayNamesMin = new DayNameCollection(DayNameLength.Minimum);
			this.monthNames = new MonthNameCollection(MonthNameLength.Normal);
			this.monthNamesShort = new MonthNameCollection(MonthNameLength.Short);
		}

		#endregion


		#region Properties: Widget initialization options

		/// <summary>
		/// The jQuery selector for another field that is to be updated with the selected date from the datepicker. Use the AltFormat property to change the format of the date within this field. Leave as blank for no alternate field.
		/// This property maps directly to the "altField" option when the jQuery UI widget is initialized.
		/// </summary>
		/// <seealso cref="AltFormat"/>
		[DefaultValue(""), Category("Data")]
		public virtual string AltField {
			get { return ViewState.GetValue("AltField", string.Empty); }
			set { ViewState["AltField"] = value; }
		}

		/// <summary>
		/// The JavaScript date format to be used for the field specified in the AltField property. This allows one date format to be shown to the user for selection purposes, while a different format is actually sent behind the scenes.
		/// This property maps directly to the "altFormat" option when the jQuery UI widget is initialized.
		/// </summary>
		/// <seealso cref="AltField"/>
		[DefaultValue(""), Category("Data")]
		public virtual string AltFormat {
			get { return ViewState.GetValue("AltFormat", string.Empty); }
			set { ViewState["AltFormat"] = value; }
		}

		/// <summary>
		/// The text to display after each date field, e.g. to show the required format.
		/// This property is HTML encoded and mapped to the "appendText" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(""), Category("Appearance")]
		public virtual string AppendText {
			get { return ViewState.GetValue("AppendText", string.Empty); }
			set { ViewState["AppendText"] = value; }
		}

		/// <summary>
		/// The URL for the popup button image. If set, ButtonText becomes the alt value and is not directly displayed.
		/// This property performs a server ResolveUrl before mapping to the "buttonImage" option when the jQuery UI widget is initialized.
		/// </summary>
		/// <seealso cref="ButtonText"/>
		/// <seealso cref="ButtonImageOnly"/>
		[DefaultValue(""), UrlProperty, Category("Appearance")]
		public virtual string ButtonImage {
			get { return ViewState.GetValue("ButtonImage", string.Empty); }
			set { ViewState["ButtonImage"] = value; }
		}

		/// <summary>
		/// Set to true to place an image after the field to use as the trigger without it appearing on a button.
		/// This property maps directly to the "buttonImageOnly" option when the jQuery UI widget is initialized.
		/// </summary>
		/// <seealso cref="ButtonText"/>
		/// <seealso cref="ButtonImageUrl"/>
		[DefaultValue(defaultButtonImageOnly), Category("Appearance")]
		public virtual bool ButtonImageOnly {
			get { return ViewState.GetValue("ButtonImageOnly", defaultButtonImageOnly); }
			set { ViewState["ButtonImageOnly"] = value; }
		}

		/// <summary>
		/// The text to display on the trigger button. Use in conjunction with ShowOn equal to Button or Both.
		/// This property is HTML encoded (if ButtonImage not provided) and mapped directly to the "buttonText" option when the jQuery UI widget is initialized.
		/// </summary>
		/// <seealso cref="ButtonImageUrl"/>
		/// <seealso cref="ButtonImageOnly"/>
		/// <seealso cref="ShowOn"/>
		[DefaultValue(defaultButtonText), Category("Appearance")]
		public virtual string ButtonText {
			get { return ViewState.GetValue("ButtonText", defaultButtonText); }
			set { ViewState["ButtonText"] = value; }
		}

		/// <summary>
		/// Allows you to click on the day names to have the week start on that day. You can disable this feature by setting the property to false.
		/// This property maps directly to the "changeFirstDay" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultChangeFirstDay), Category("Behavior")]
		public virtual bool ChangeFirstDay {
			get { return ViewState.GetValue("ChangeFirstDay", defaultChangeFirstDay); }
			set { ViewState["ChangeFirstDay"] = value; }
		}

		/// <summary>
		/// Allows you to change the month by selecting from a drop-down list. You can disable this feature by setting the property to false.
		/// This property maps directly to the "changeMonth" option when the jQuery UI widget is initialized.
		/// </summary>
		/// <seealso cref="ChangeYear"/>
		[DefaultValue(defaultChangeMonth), Category("Behavior")]
		public virtual bool ChangeMonth {
			get { return ViewState.GetValue("ChangeMonth", defaultChangeMonth); }
			set { ViewState["ChangeMonth"] = value; }
		}

		/// <summary>
		/// Allows you to change the year by selecting from a drop-down list. You can disable this feature by setting the property to false.
		/// This property maps directly to the "changeYear" option when the jQuery UI widget is initialized.
		/// </summary>
		/// <seealso cref="ChangeMonth"/>
		[DefaultValue(defaultChangeYear), Category("Behavior")]
		public virtual bool ChangeYear {
			get { return ViewState.GetValue("ChangeYear", defaultChangeYear); }
			set { ViewState["ChangeYear"] = value; }
		}

		/// <summary>
		/// The text to display for the close link. This attribute is one of the regionalisation attributes.
		/// This property is HTML encoded and mapped to the "closeText" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultCloseText), Category("Appearance")]
		public virtual string CloseText {
			get { return ViewState.GetValue("CloseText", defaultCloseText); }
			set { ViewState["CloseText"] = value; }
		}

		/// <summary>
		/// Whether or not the user input will be constrained.
		/// This property maps directly to the "constrainInput" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultConstrainInput), Category("Behavior")]
		public virtual bool ConstrainInput {
			get { return ViewState.GetValue("ConstrainInput", defaultConstrainInput); }
			set { ViewState["ConstrainInput"] = value; }
		}

		/// <summary>
		/// The text to display for the current day link. This attribute is one of the regionalisation attributes.
		/// This property is HTML encoded and mapped to the "currentText" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultCurrentText), Category("Appearance")]
		public virtual string CurrentText {
			get { return ViewState.GetValue("CurrentText", defaultCurrentText); }
			set { ViewState["CurrentText"] = value; }
		}

		//TODO: Bridge .NET to JavaScript DateFormat
		//[DefaultValue(defaultDateFormat), Category("Appearance")]
		//public virtual string DateFormat {
		//    get { return ViewState["DateFormat"] as string ?? defaultDateFormat; }
		//    set { ViewState["DateFormat"] = value; }
		//}

		/// <summary>
		/// A list of long day names for use as requested via the DateFormat setting. They also appear as popup hints when hovering over the corresponding column headings. This attribute is one of the regionalisation attributes.
		/// This property is converted and mapped to the "dayNames" option when the jQuery UI widget is initialized.
		/// </summary>
		[Category("Appearance"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
		public virtual DayNameCollection DayNames {
			get { return this.dayNames; }
		}

		/// <summary>
		/// A list of abbreviated day names for use as requested via the DateFormat property. This attribute is one of the regionalisation attributes.
		/// This property is converted and mapped to the "dayNamesShort" option when the jQuery UI widget is initialized.
		/// </summary>
		[Category("Appearance"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
		public virtual DayNameCollection DayNamesShort {
			get { return this.dayNamesShort; }
		}

		/// <summary>
		/// A list of minimised day names, starting from Sunday, for use as column headers within the datepicker. This attribute is one of the regionalisation attributes.
		/// This property is converted and mapped to the "dayNamesMin" option when the jQuery UI widget is initialized.
		/// </summary>
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
		public virtual DayNameCollection DayNamesMin {
			get { return this.dayNamesMin; }
		}

		/// <summary>
		/// Set the date to display on first opening if the field is blank.
		/// This property is mapped to the "defaultDate" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(null), Category("Behavior")]
		public virtual DateTime? DefaultDate {
			get { return ViewState.GetNullableValue<DateTime>("DefaultDate"); }
			set { ViewState.SetNullableValue("DefaultDate", value); }
		}

		/// <summary>
		/// Controls the speed at which the datepicker appears.  This is a named speed ("Normal", "Fast" or "Slow") a number of milliseconds or immediate.
		/// This property is directly mapped to the "duration" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(typeof(Duration), defaultDurationString), Category("Behavior")]
		public virtual Duration Duration {
			get { return ViewState.GetValue("Duration", defaultDuration); }
			set { ViewState["Duration"] = value; }
		}

		/// <summary>
		/// Set the first day of the week: Sunday is 0, Monday is 1, ... This attribute is one of the regionalisation attributes.
		/// This property maps directly to the "firstDay" option when the jQuery UI widget is initialized.
		/// </summary>
		/// <exception cref="ArgumentOutOfRangeException">Value must be between 0 and 6, inclusive.</exception>
		[DefaultValue(defaultFirstDay), Category("Appearance")]
		public virtual int FirstDay {
			get { return ViewState.GetValue("FirstDay", defaultFirstDay); }
			set {
				if(value < 0 || value > 6) throw new ArgumentOutOfRangeException("value", value, "Value must be between 0 and 6, inclusive.");
				ViewState["FirstDay"] = value;
			}
		}

		/// <summary>
		/// If true, the current day link moves to the currently selected date instead of today.
		/// This property maps directly to the "gotoCurrent" option when the jQuery UI widget is initialized.
		/// </summary>
		/// <seealso cref="CurrentText"/>
		[DefaultValue(defaultGoToCurrent), Category("Behavior")]
		public virtual bool GoToCurrent {
			get { return ViewState.GetValue("GoToCurrent", defaultGoToCurrent); }
			set { ViewState["GoToCurrent"] = value; }
		}

		/// <summary>
		/// Normally the previous and next links are disabled when not applicable (see MinDate/MaxDate). You can hide them altogether by setting this attribute to true.
		/// This property maps directly to the "hideIfNoPrevNext" option when the jQuery UI widget is initialized.
		/// </summary>
		/// <seealso cref="MinDate"/>
		/// <seealso cref="MaxDate"/>
		[DefaultValue(defaultHideIfNoPrevNext), Category("Behavior")]
		[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Prev", Justification = "Exposed as the jQuery option name.")]
		public virtual bool HideIfNoPrevNext {
			get { return ViewState.GetValue("HideIfNoPrevNext", defaultHideIfNoPrevNext); }
			set { ViewState["HideIfNoPrevNext"] = value; }
		}

		/// <summary>
		/// True if the current language is drawn from right to left. This attribute is one of the regionalisation attributes.
		/// This property maps directly to the "isRTL" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultIsRtl), Category("Behavior")]
		[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Rtl", Justification = "Exposed as the jQuery option name.")]
		public virtual bool IsRtl {
			get { return ViewState.GetValue("IsRtl", defaultIsRtl); }
			set { ViewState["IsRtl"] = value; }
		}

		/// <summary>
		/// Sets a maximum selectable date.
		/// When specified, this property creates a JavaScript Date object mappped to the "maxDate" option when the jQuery UI widget is initialized.
		/// </summary>
		/// <seealso cref="MinDate"/>
		[DefaultValue(null), Category("Behavior")]
		public virtual DateTime? MaxDate {
			get { return ViewState.GetNullableValue<DateTime>("MaxDate"); }
			set { ViewState.SetNullableValue("MaxDate", value); }
		}

		/// <summary>
		/// Sets a minimum selectable date.
		/// When specified, this property creates a JavaScript Date object mappped to the "maxDate" option when the jQuery UI widget is initialized.
		/// </summary>
		/// <seealso cref="MaxDate"/>
		[DefaultValue(null), Category("Behavior")]
		public virtual DateTime? MinDate {
			get { return ViewState.GetNullableValue<DateTime>("MinDate"); }
			set { ViewState.SetNullableValue("MinDate", value); }
		}

		/// <summary>
		/// The list of full month names, as used in the month header on each datepicker and as requested via the DateFormat property. This attribute is one of the regionalisation attributes.
		/// This property is converted and mapped to the "monthNames" option when the jQuery UI widget is initialized.
		/// </summary>
		[Category("Appearance"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
		public virtual MonthNameCollection MonthNames {
			get { return this.monthNames; }
		}

		/// <summary>
		/// The list of abbreviated month names, for use as requested via the DateFormat property. This attribute is one of the regionalisation attributes.
		/// This property is converted and mapped to the "monthNamesShort" option when the jQuery UI widget is initialized.
		/// </summary>
		/// <exception cref="ArgumentException">There must be twelve items separated by vertical bars.</exception>
		[Category("Appearance"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
		public virtual MonthNameCollection MonthNamesShort {
			get { return this.monthNamesShort; }
		}

		/// <summary>
		/// When true the FormatDate function is applied to the PrevText, NextText, and CurrentText values before display, allowing them to display the target month names for example.
		/// This property maps directly to the "navigationAsDateFormat" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultNavigationAsDateFormat), Category("Appearance")]
		public virtual bool NavigationAsDateFormat {
			get { return ViewState.GetValue("NavigationAsDateFormat", defaultNavigationAsDateFormat); }
			set { ViewState["NavigationAsDateFormat"] = value; }
		}

		/// <summary>
		/// The text to display for the next month link. This attribute is one of the regionalisation attributes.
		/// This property maps directly to the "nextText" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultNextText), Category("Appearance")]
		public virtual string NextText {
			get { return ViewState.GetValue("NextText", defaultNextText); }
			set { ViewState["NextText"] = value; }
		}

		/// <summary>
		/// In combination with RowCount, sets how many months to show at once.
		/// This property maps along with RowCount to the "numberOfMonths" option when the jQuery UI widget is initialized.
		/// </summary>
		/// <seealso cref="ColumnCount"/>
		/// <seealso cref="StepMonths"/>
		/// <exception cref="ArgumentOutOfRangeException">Value must be between 1 and 12, inclusive.</exception>
		[DefaultValue(defaultColumnCount), Category("Appearance")]
		public virtual int ColumnCount {
			get { return ViewState.GetValue("ColumnCount", defaultColumnCount); }
			set {
				if(value < 1 || value > 12) throw new ArgumentOutOfRangeException("value", value, "Value must be between 1 and 12, inclusive.");
				ViewState["ColumnCount"] = value; 
			}
		}

		/// <summary>
		/// In combination with ColumnCount, sets how many months to show at once.
		/// This property maps along with ColumnCount to the "numberOfMonths" option when the jQuery UI widget is initialized.
		/// </summary>
		/// <seealso cref="RowCount"/>
		/// <seealso cref="StepMonths"/>
		/// <exception cref="ArgumentOutOfRangeException">Value must be between 1 and 12, inclusive.</exception>
		[DefaultValue(defaultRowCount), Category("Appearance")]
		public virtual int RowCount {
			get { return ViewState.GetValue("RowCount", defaultRowCount); }
			set {
				if (value < 1 || value > 12) throw new ArgumentOutOfRangeException("value", value, "Value must be between 1 and 12, inclusive.");
				ViewState["RowCount"] = value; 
			}
		}

		/// <summary>
		/// The text to display for the previous month link. This attribute is one of the regionalisation attributes.
		/// This property maps directly to the "nextText" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultPrevText), Category("Appearance")]
		[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Prev", Justification = "Exposed as the jQuery option name.")]
		public virtual string PrevText {
			get { return ViewState.GetValue("PrevText", defaultPrevText); }
			set { ViewState["PrevText"] = value; }
		}

		/// <summary>
		/// Sets the cutoff year for determining the century for a date (used in conjunction with DateFormat 'y'). If an unsigned numeric value through 99 is provided then this value is used directly. If a the value is preceded with a + or -, then it is converted to a number and added to the current year. Once the cutoff year is calculated, any dates entered with a year value less than or equal to it are considered to be in the current century, while those greater than it are deemed to be in the previous century.
		/// This property maps directly to the "shortYearCutoff" option when the jQuery UI widget is initialized.
		/// </summary>
		/// <exception cref="ArgumentOutOfRangeException">Value must be between -99 and 99, inclusive.</exception>
		[DefaultValue(typeof(RelativeValue), defaultShortYearCutoffString), Category("Behavior")]
		public virtual RelativeValue ShortYearCutoff {
			get { return ViewState.GetValue("ShortYearCutoff", defaultShortYearCutoff);
			}
			set {
				if(value.Value < -99 || value.Value > 99) throw new ArgumentOutOfRangeException("value", value, "Value must be between -99 and 99, inclusive.");
				ViewState["ShortYearCutoff"] = value; 
			}
		}

		/// <summary>
		/// Set the name of the animation used to show/hide the datepicker. Use "show" (the default), "slideDown", "fadeIn", or any of the show/hide jQuery UI effects.
		/// This property maps directly to the "showAnim" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultShowAnim), Category("Appearance")]
		[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Anim", Justification = "Exposed as the jQuery option name.")]
		public virtual string ShowAnim {
			get { return ViewState.GetValue("ShowAnim", defaultShowAnim); }
			set { ViewState["ShowAnim"] = value; }
		}

		/// <summary>
		/// Whether to show the button panel.
		/// This property maps directly to the "showButtonPanel" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultShowButtonPanel), Category("Appearance")]
		public virtual bool ShowButtonPanel {
			get { return ViewState.GetValue("ShowButtonPanel", defaultShowButtonPanel); }
			set { ViewState["ShowButtonPanel"] = value; }
		}

		/// <summary>
		/// Have the datepicker appear automatically when the field receives focus ("Focus"), appear only when a button is clicked ("Button"), or appear when either event takes place ("Both").
		/// This property maps directly to the "showOn" option when the jQuery UI widget is initialized.
		/// </summary>
		/// <exception cref="ArgumentException">Value not defined.</exception>
		[DefaultValue(defaultShowOn), Category("Behavior")]
		public virtual DropDownShowOn ShowOn {
			get { return ViewState.GetValue("ShowOn", defaultShowOn);
			}
			set {
				if(!Enum.IsDefined(typeof(DropDownShowOn), value)) throw new ArgumentException("Value not defined.", "value");
				ViewState["ShowOn"] = value;
			}
		}

		/// <summary>
		/// If using one of the jQuery UI effects for ShowAnim, you can provide additional settings for that animation via this option.
		/// This value is direct access to the jQuery JavaScript code and should be encoded accordingly.  Do not include the braces.
		/// This property maps directly to the "showOptions" option when the jQuery UI widget is initialized.
		/// </summary>
		/// <example>direction: 'up'</example>
		[DefaultValue(""), Category("Appearance")]
		public virtual string ShowOptions {
			get { return ViewState.GetValue("ShowOptions", string.Empty); }
			set { ViewState["ShowOptions"] = value; }
		}

		/// <summary>
		/// Whether or not to display dates in other months (non-selectable) at the start or end of the current month.
		/// This property maps directly to the "showOtherMonths" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultShowOtherMonths), Category("Appearance")]
		public virtual bool ShowOtherMonths {
			get { return ViewState.GetValue("ShowOtherMonths", defaultShowOtherMonths); }
			set { ViewState["ShowOtherMonths"] = value; }
		}

		/// <summary>
		/// Set how many months to move when clicking the Previous/Next links.  This is useful in order to achieve the desired behavior when using multiple rows and/or columns.
		/// This property maps directly to the "stepMonths" option when the jQuery UI widget is initialized.
		/// </summary>
		/// <exception cref="ArgumentOutOfRangeException">Value must be positive.</exception>
		/// <seealso cref="RowCount"/>
		/// <seealso cref="ColumnCount"/>
		[DefaultValue(defaultStepMonths), Category("Behavior")]
		public virtual int StepMonths {
			get { return ViewState.GetValue("StepMonths", defaultStepMonths); }
			set {
				if (value <= 0) throw new ArgumentOutOfRangeException("value", value, "Value must be positive.");
				ViewState["StepMonths"] = value; 
			}
		}

		#endregion


		#region Properties: Additional

		/// <summary>
		/// Gets or sets the value of the date in the input control.
		/// </summary>
		[DefaultValue(null), Category("Data")]
		public virtual DateTime? SelectedDate {
			get { return ViewState.GetNullableValue<DateTime>("SelectedDate"); }
			set { ViewState.SetNullableValue("SelectedDate", value); }
		}

		/// <summary>
		/// Gets or set whether the user is able to modify the value in the input control.
		/// </summary>
		[DefaultValue(false), Category("Behavior")]
		[SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "ReadOnly", Justification = "Matches property used by System.Web.UI.WebControls.TextBox.")]
		public virtual bool ReadOnly {
			get { return ViewState.GetValue("ReadOnly", false); }
			set { ViewState["ReadOnly"] = value; }
		}

		#endregion


		#region Events

		/// <summary>
		/// Occurs on postback when the Date property value has changed since previous render.
		/// </summary>
		[Category("Action")]
		public event EventHandler DateChanged;

		#endregion


		#region Methods: Event hooks

		/// <summary>
		/// Raises the DateChanged event.
		/// </summary>
		/// <param name="e"></param>
		protected void OnDateChanged(EventArgs e) {
			if(DateChanged != null) DateChanged.Invoke(this, e);
		}

		#endregion


		#region Methods: Rendering hooks

		/// <summary>
		/// Allows a jQuery server control to reference scripts and stylesheets necessary to render their client markup and widgets.
		/// </summary>
		/// <param name="manager">The GreenIsomer.Web.Controls.JQuery.Manager used to handle style and script referencing in addition to unified generation of load and ready scripts.</param>
		protected override void Register(Manager manager) {

			manager.RegisterScriptFile(Settings.Default.CoreScriptFile);
			manager.RegisterScriptFile(Settings.Default.UiCoreScriptFile);
			manager.RegisterScriptFile(Settings.Default.UiDatePickerScriptFile);

			manager.RegisterStyleFile(Settings.Default.UiCoreStyleFile);
			manager.RegisterStyleFile(Settings.Default.UiDatePickerStyleFile);
			manager.RegisterThemeStyleFile();

			manager.RegisterControl(this);
		}

		/// <summary>
		/// Raises the System.Web.UI.Control.PreRender event.
		/// </summary>
		/// <param name="e">The System.EventArgs object that contains the event data.</param>
		protected override void OnPreRender(EventArgs e) {
			base.OnPreRender(e);

			Page.RegisterRequiresPostBack(this);
		}

		/// <summary>
		/// Adds HTML attributes and styles that need to be rendered to the specified System.Web.UI.HtmlTextWriter.
		/// This property is used primarily by control developers.
		/// </summary>
		/// <param name="writer">The System.Web.UI.HtmlTextWriter object that receives the rendered content.</param>
		protected override void AddAttributesToRender(HtmlTextWriter writer) {

			Page.ClientScript.RegisterForEventValidation(UniqueID);

			writer.AddAttribute(HtmlTextWriterAttribute.Name, UniqueID);
			writer.AddAttribute(HtmlTextWriterAttribute.Type, "text");
			writer.AddAttribute(HtmlTextWriterAttribute.Value, GetText(SelectedDate));

			base.AddAttributesToRender(writer);

			if(ReadOnly) writer.AddAttribute(HtmlTextWriterAttribute.ReadOnly, "readonly");
			writer.AddAttribute(HtmlTextWriterAttribute.AutoComplete, "off");
		}

		/// <summary>
		/// Renders the load script required to generate the client-side jQuery widget.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A GreenIsomer.Web.Controls.JQuery.ScriptBuilder that creates the output stream to render jQuery JavaScript on the client.</param>
		protected override void RenderLoadScript(ScriptBuilder script) {

			if(!Enabled || ReadOnly) return;

			script.RenderSelector(ClientID);
			script.Append(".datepicker(");
			script.RenderProperties(CreateProperties());
			script.Append(");");
		}

		/// <summary>
		/// Creates the properties object that will be rendered as the jQuery UI initialization options.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <returns>A GreenIsomer.Web.Controls.JQuery.Properties object that will become the jQuery UI initialization options.</returns>
		protected override PropertyCollection CreateProperties() {
			PropertyCollection properties = base.CreateProperties();

			string altField = AltField;
			if(!string.IsNullOrEmpty(altField)) properties.AddValue("altField", altField);

			string altFormat = AltFormat;
			if(!string.IsNullOrEmpty(altFormat)) properties.AddValue("altFormat", altFormat);

			string appendText = AppendText;
			if(!string.IsNullOrEmpty(appendText)) properties.AddValue("appendText", appendText, true);

			string buttonImage = ButtonImage;
			if(!string.IsNullOrEmpty(buttonImage)) properties.AddValue("buttonImage", ResolveUrl(buttonImage));

			bool buttonImageOnly = ButtonImageOnly;
			if(buttonImageOnly != defaultButtonImageOnly) properties.AddValue("buttonImageOnly", buttonImageOnly);

			string buttonText = ButtonText;
			if(buttonText != defaultButtonText) properties.AddValue("buttonText", buttonText, string.IsNullOrEmpty(buttonImage));

			bool changeFirstDay = ChangeFirstDay;
			if(changeFirstDay != defaultChangeFirstDay) properties.AddValue("changeFirstDay", changeFirstDay);

			bool changeMonth = ChangeMonth;
			if(changeMonth) properties.AddValue("changeMonth", changeMonth);

			bool changeYear = ChangeYear;
			if(changeYear) properties.AddValue("changeYear", changeYear);

			string closeText = CloseText ?? string.Empty;
			if(closeText != defaultCloseText) properties.AddValue("closeText", closeText, true);

			bool constrainInput = ConstrainInput;
			if(constrainInput != defaultConstrainInput) properties.AddValue("constrainInput", constrainInput);

			string currentText = CurrentText ?? string.Empty;
			if(currentText != defaultCurrentText) properties.AddValue("currentText", currentText, true);

			//TODO: Bridge .NET to JavaScript DateFormat
			//string dateFormat = DateFormat ?? string.Empty;
			//if(dateFormat != defaultDateFormat) properties.AddValue("dateFormat", Manager.GetJavaScriptDateFormat(dateFormat));

			if(DayNames.Culture != Culture.Invariant)
				properties.AddValue("dayNames", dayNames.ToArray());

			if(DayNamesMin.Culture != Culture.Invariant)
				properties.AddValue("dayNamesMin", dayNamesMin.ToArray());

			if(DayNamesShort.Culture != Culture.Invariant)
				properties.AddValue("dayNamesShort", dayNamesShort.ToArray());

			Duration duration = Duration;
			if(duration != defaultDuration) properties.AddValue("duration", duration);

			int firstDay = FirstDay;
			if(firstDay != defaultFirstDay) properties.AddValue("firstDay", firstDay);

			bool goToCurrent = GoToCurrent;
			if(goToCurrent != defaultGoToCurrent) properties.AddValue("gotoCurrent", goToCurrent);

			bool isRtl = IsRtl;
			if(isRtl != defaultIsRtl) properties.AddValue("isRTL", isRtl);

			DateTime? maxDate = MaxDate;
			if(maxDate.HasValue) properties.AddValue("maxDate", maxDate.Value, false);

			DateTime? minDate = MinDate;
			if(minDate.HasValue) properties.AddValue("minDate", minDate.Value, false);

			if(MonthNames.Culture != Culture.Invariant)
				properties.AddValue("monthNames", monthNames.ToArray());

			if(MonthNamesShort.Culture != Culture.Invariant)
				properties.AddValue("monthNamesShort", monthNamesShort.ToArray());

			bool navigationAsDateFormat = NavigationAsDateFormat;
			if(navigationAsDateFormat != defaultNavigationAsDateFormat) properties.AddValue("navigationAsDateFormat", navigationAsDateFormat);

			string nextText = NextText;
			if(nextText != defaultNextText) properties.AddValue("nextText", nextText);

			int rowCount = RowCount;
			int columnCount = ColumnCount;
			if(rowCount != defaultRowCount || columnCount != defaultColumnCount)
				properties.AddValue("numberOfMonths", new int[] { rowCount, columnCount });

			string prevText = PrevText;
			if(prevText != defaultPrevText) properties.AddValue("prevText", prevText);

			RelativeValue shortYearCutoff = ShortYearCutoff;
			if(shortYearCutoff != defaultShortYearCutoff) properties.AddValue("shortYearCutoff", shortYearCutoff);

			string showAnim = ShowAnim;
			if(showAnim != defaultShowAnim) properties.AddValue("showAnim", showAnim);

			bool showButtonPanel = ShowButtonPanel;
			if(showButtonPanel != defaultShowButtonPanel) properties.AddValue("showButtonPanel", showButtonPanel);

			DropDownShowOn showOn = ShowOn;
			if(showOn != defaultShowOn) properties.AddEnumValue("showOn", showOn);

			string showOptions = ShowOptions;
			if(!string.IsNullOrEmpty(showOptions)) properties.AddValue("showOptions", "{ " + showOptions + " }");

			bool showOtherMonths = ShowOtherMonths;
			if(showOtherMonths != defaultShowOtherMonths) properties.AddValue("showOtherMonths", showOtherMonths);

			int stepMonths = StepMonths;
			if(stepMonths != defaultStepMonths) properties.AddValue("stepMonths", stepMonths);

			return properties;
		}

		/// <summary>
		/// Processes postback data for an ASP.NET server control.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="postDataKey">The key identifier for the control.</param>
		/// <param name="postCollection">The collection of all incoming name values.</param>
		/// <returns>True if the server control's state changes as a result of the postback; otherwise, false.</returns>
		protected virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection) {

			Page.ClientScript.ValidateEvent(postDataKey);

			DateTime? date = SelectedDate;
			DateTime? newDate = ConstrainDate(ParseDate(postCollection[postDataKey]));

			if(newDate == date) return false;

			SelectedDate = newDate;
			return true;
		}

		#endregion


		#region Routines

		private DateTime? ConstrainDate(DateTime? date) {

			if(date == null) return date;

			DateTime? minDate = MinDate;
			if(minDate.HasValue && date < minDate.Value) return null;

			DateTime? maxDate = MaxDate;
			if(maxDate.HasValue && date > maxDate.Value) return null;

			return date;
		}

		#endregion


		#region Designer Methods

		private bool ShouldSerializeDayNames() {
			return this.DayNames.Culture != Culture.Invariant;
		}

		private bool ShouldSerializeDayNamesShort() {
			return this.DayNamesShort.Culture != Culture.Invariant;
		}

		private bool ShouldSerializeDayNamesMin() {
			return this.DayNamesMin.Culture != Culture.Invariant;
		}

		private bool ShouldSerializeMonthNames() {
			return this.MonthNames.Culture != Culture.Invariant;
		}

		private bool ShouldSerializeMonthNamesShort() {
			return this.MonthNamesShort.Culture != Culture.Invariant;
		}

		#endregion


		#region Explicit implementation: IPostBackDataHandler

		[SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Justification = "It's called OnDateChanged.")]
		void IPostBackDataHandler.RaisePostDataChangedEvent() {
			OnDateChanged(EventArgs.Empty);
		}

		bool IPostBackDataHandler.LoadPostData(string postDataKey, NameValueCollection postCollection) {
			return LoadPostData(postDataKey, postCollection);
		}

		#endregion


	}

}